﻿/*
 * Alain Metge 2011
 * http://www.dotnetrangers.net
 * See license.txt
 */
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.IO;
using System.Linq;

namespace VsxFactory.Repository.VsixGallery
{
    /// <summary>
    /// Very basic implementation using unsupported Microsoft.VisualStudio.ExtensionManager.Implementation.
    /// You must add a reference to this assembly located in the Visual Studio Private assemblies Folder (...\Microsoft Visual Studio 10.0\Common7\IDE\PrivateAssemblies)
    /// </summary>
    public class VsixGalleryDataContext
    {
        private const string CacheKey = "__PACKAGES__";
        private string _baseFolder;
        private object _syncObject = new object();

        public VsixGalleryDataContext()
        {
            _baseFolder = System.Web.HttpContext.Current.Server.MapPath("~/App_Data/Extensions");
        }

        /// <summary>
        /// Get packages from disk and store them in local cache.
        /// </summary>
        public IQueryable<VsixPackage> Packages
        {
            get
            {
                var cache = PackageCache;
                var newCache = new Dictionary<string, VsixPackage>();

                // Check for new or deleted files.
                // Do not take into account new version of an existing file 
                // It's a very basic implementation.
                foreach (var file in Directory.GetFiles(_baseFolder, "*.vsix"))
                {
                    if (cache == null || !cache.ContainsKey(file))
                    {
                        try
                        {
                            newCache.Add(file, GetPackageFromFile(file));
                        }
                        catch (Exception ex)
                        {
                            Trace.WriteLine(ex.Message);
                        }
                    }
                    else if (cache != null)
                    {
                        newCache.Add(file, cache[file]);
                    }
                }

                if (cache == null || cache.Count != newCache.Count)
                {
                    lock (_syncObject)
                    {
                        PackageCache = cache = newCache;
                    }
                }
                return cache.Values.AsQueryable();
            }
        }

        /// <summary>
        /// Asp.net cache 
        ///  aspNetCompatibilityEnabled must be set to true.
        /// </summary>
        private Dictionary<string,VsixPackage> PackageCache
        {
            get { return (Dictionary<string, VsixPackage>)System.Web.HttpContext.Current.Cache[CacheKey]; }
            set { System.Web.HttpContext.Current.Cache[CacheKey] = value; }
        }

        /// <summary>
        /// Load the package manifest from file
        /// </summary>
        /// <param name="fileName"></param>
        /// <returns></returns>
        VsixPackage GetPackageFromFile(string fileName)
        {
            Contract.Requires(!String.IsNullOrEmpty(fileName));

            var manifestUri = new Uri("/extension.vsixmanifest", UriKind.Relative);

            using (var vsixStream = new FileStream(fileName, FileMode.Open, FileAccess.Read))
            {
                Microsoft.VisualStudio.ExtensionManager.Implementation.VsixSerializer ser = new Microsoft.VisualStudio.ExtensionManager.Implementation.VsixSerializer();
                System.IO.Packaging.ZipPackage package = null;
                try
                {
                    package = System.IO.Packaging.Package.Open(vsixStream, FileMode.Open, FileAccess.Read) as System.IO.Packaging.ZipPackage;
                }
                catch (Exception exception)
                {
                    throw new Exception(string.Format("Invalid package {0} - {1}", Path.GetFileName(fileName), exception.Message));
                }

                if (!package.PartExists(manifestUri))
                {
                    throw new Exception(string.Format("Missing manifest in package {0}", Path.GetFileName(fileName)));
                }

                Stream vsixManifestStream = package.GetPart(manifestUri).GetStream(FileMode.Open, FileAccess.Read);
                var data = (Microsoft.VisualStudio.ExtensionManager.Implementation.Vsix)ser.Deserialize(vsixManifestStream);
                var vsix = new VsixPackage();
                vsix.Author = data.Identifier.Author;
                vsix.Name = data.Identifier.Name;
                vsix.Description = data.Identifier.Description;
                vsix.Version = data.Identifier.Version;
                vsix.Icon = data.Identifier.Icon;
                vsix.MoreInfoUrl = data.Identifier.MoreInfoUrl;
                vsix.FileName = Path.GetFileName(fileName);

                string str = string.Empty;
                foreach(var reference in data.References)
                {
                    str = str + EscapeString(reference.Name) + @"\";
                    str = str + EscapeString(reference.Id) + @"\";
                    if (reference.MoreInfoUrl != null)
                    {
                        str = str + EscapeString(reference.MoreInfoUrl.ToString());
                    }
                    str = str + "[";
                    if (reference.MinVersion != null)
                    {
                        str = str + reference.MinVersion.ToString();
                    }
                    str = str + ",";
                    if (reference.MaxVersion != null)
                    {
                        str = str + reference.MaxVersion.ToString();
                    }
                    str = str + "]";
                    if (!string.IsNullOrEmpty(reference.VsixPath))
                    {
                        str = str + "n";
                    }
                    str = str + ";";
                }
                vsix.References = str;
                return vsix;
            }
        }

        private string EscapeString(string s)
        {
            Contract.Requires(s != null);

            char[] chArray = new char[s.Length * 3];
            int length = 0;
            foreach (char ch in s)
            {
                if (Path.GetInvalidFileNameChars().Contains<char>(ch) || (ch == '%'))
                {
                    foreach (char ch2 in Uri.HexEscape(ch))
                    {
                        chArray[length++] = ch2;
                    }
                }
                else
                {
                    chArray[length++] = ch;
                }
            }
            return new string(chArray, 0, length);

        }


        public string PackageFolder { get { return _baseFolder; } }
    }
}